<?php
declare (strict_types=1);

namespace quick\admin\library\service;

use app\common\model\SystemAttachmentDisk;
use quick\admin\library\storage\AliossStorage;
use quick\admin\library\storage\LocalStorage;
use quick\admin\library\storage\Storage;
use quick\admin\library\storage\TxcosStorage;
use quick\admin\Service;
use think\Exception;
use think\file\UploadedFile;

/**
 * Class UploadService
 * @package quick\admin\library\service
 */
class UploadService extends Service
{

    public $uptype = "local";

    /**
     * @var UploadedFile
     */
    public $file;

    /**
     *  安全模式
     *
     * @var bool
     */
    public $safe = false;


    /**
     * 文件类型
     *
     * @var
     */
    public $extension;


    private array $config = [];

    private Storage $storage;

    /**
     * 文件名前缀
     * @var string
     */
    public string $pre = '';


    public static $storageList = [
        'local' =>[
            'name' => '本地',
            'class' => LocalStorage::class
        ],
        'alioss' => [
            'name' => '阿里云',
            'class' => AliossStorage::class,
        ],

        'txcos' => [
            'name' => '腾讯云',
            'class' => TxcosStorage::class
        ],
    ];


    /**
     * 添加storage
     * @param string $name key
     * @param string $storageClass 类名称
     * @param string $title 名称
     * @return void
     */
    public static function addStorage(string $name,string $storageClass,string $title = '')
    {
        if(!is_subclass_of($storageClass,Storage::class)){
           throw new \Exception($storageClass." class must inherit from ".Storage::class." class");
        }
        static::$storageList[$name] = [
            'class' => $storageClass,
            'name' => empty($title) ? $name:$title,
        ];
    }


    /**
     *
     * @param string $type 上传类型
     * @param array $config 配置
     * @return $this
     */
    public function setConfig(string $type,array $config)
    {
        $this->uptype = $type;
        $this->config = $config;

        return $this;
    }


    /**
     * 获取储存引擎
     * @return Storage
     * @throws \Exception
     */
    public function storage():Storage
    {
        if(!empty($this->storage)){
            return $this->storage;
        }
        if(empty(static::$storageList[$this->uptype])){
            throw new \Exception("储存引擎不存在:".$this->uptype);
        }
        $this->storage = invoke(static::$storageList[$this->uptype]['class'],[$this->config]);
        return $this->storage;
    }



    /**
     * 上传文件
     *
     * @param UploadedFile $file
     * @return $this
     */
    public function setFile(UploadedFile $file)
    {
        $this->file = $file;
        return $this;
    }


    /**
     * 文件类型
     *
     * @return string
     * @throws Exception
     */
    public function getExtension()
    {
        $this->extension = strtolower($this->file->getOriginalExtension());
        return $this->extension;
    }

    /**
     * 文件名称
     *
     * @return string
     * @throws Exception
     */
    public function getName(string $pre = '')
    {
        return Storage::name($this->getFile()->getPathname(), $this->getExtension(), $pre, 'md5_file');
    }


    public function save()
    {

        if (!($file = $this->getFile()) || empty($file)) {
            throw new Exception('文件上传异常，文件可能过大或未上传');
        }


        $size = filesize($file->getRealPath());
        $extension = $this->getExtension();


        $pre = $this->pre == 'admin' ? sysConfig('storage.storage_dir_prefix'):$this->pre;
        $saveName = $this->getName($pre);


        if (strpos($saveName, '../') !== false) {
            throw new \Exception('文件路径不能出现跳级操作！');
        }

        if (strtolower(pathinfo(parse_url($saveName, PHP_URL_PATH), PATHINFO_EXTENSION)) !== $extension) {
            throw new \Exception('文件后缀异常，请重新上传文件！');
        }

        $exts = sysConfig('storage.allow_exts');
        if($exts){
            $exts = strtolower(str_replace('，',',',$exts));
            if (!in_array($this->extension, explode(',', $exts))) {
                throw new Exception('文件上传类型受限，请在后台配置');
            }
        }

        if (in_array($extension, ['sh', 'asp', 'bat', 'cmd', 'exe', 'php'])) {
            throw new \Exception('文件安全保护，禁止上传可执行文件！');
        }

        $storageService = $this->storage();
        if ($this->uptype === 'local') {


            $distName = $storageService->savePath($saveName, $this->safe);
            $file->move(dirname($distName), basename($distName));

            if (in_array($extension, ['jpg', 'gif', 'png', 'bmp', 'jpeg', 'wbmp'])) {

                if ($storageService->imgNotSafe($distName) && $storageService->del($saveName,$this->safe)) {
                    throw new \Exception('图片未通过安全检查！');
                }

                [$width, $height] = getimagesize($distName);
                if (($width < 1 || $height < 1) && $storageService->del($saveName,$this->safe)) {
                    throw new \Exception('读取图片的尺寸失败！');
                }
            }

            $info = $storageService->info($saveName, $this->safe, $file->getOriginalName());

        } else {

            $bina = file_get_contents($file->getRealPath());
            $info = $storageService->save($saveName, $bina, $this->safe, $file->getOriginalName());

        }
        if (!is_array($info) || !isset($info['url'])) {
            throw new \Exception('文件处理失败，请稍候再试！');
        }
        return [
            'uploaded' => true,
            'filename' => $saveName,
            'size' => $size,
            'name' => $this->getFile()->getOriginalName(),
            'url' => $this->safe ? $saveName : $info['url']
        ];

    }

    /**
     * 获取文件上传类型
     * @return boolean
     */
    private function getSafe(): bool
    {
        return boolval(input('safe', '0'));
    }

    /**
     * 获取文件上传方式
     * @return string
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    private function getType(): string
    {
        $this->uptype = strtolower(input('uptype', ''));
        if (!in_array($this->uptype, ['local', 'qiniu', 'alioss', 'txcos'])) {
            $this->uptype = 'local';//strtolower(sysconf('storage.type'));
        }
        return strtolower($this->uptype);
    }

    /**
     * 获取本地文件对象
     * @return UploadedFile
     */
    private function getFile(): UploadedFile
    {
        return $this->file;
    }



}
